/*
 * NyuGC.cpp
 *
 *  Created on: 2010/05/26
 *      Author: mitsu
 */

#include <set>
#include <list>
#include <algorithm>
#include "NyuGC.hpp"
#include "NyuContext.hpp"
#include "NyuObject.hpp"

namespace {

struct AllocInfo {
    int remembered :1;
    int forwarded :1;
    int age :28;
    unsigned int size :32;
};

const unsigned int NEW_AREA_SIZE = 1024 * 120;
const unsigned int SUR_AREA_SIZE = 1024 * 4;
const unsigned int YOUNG_HEAP_SIZE = NEW_AREA_SIZE + SUR_AREA_SIZE * 2;

const int AGE_MAX = 3;

char young_heap_[YOUNG_HEAP_SIZE];
unsigned int new_free_;
unsigned int from_sur_start_ = NEW_AREA_SIZE;
unsigned int to_sur_start_ = NEW_AREA_SIZE + SUR_AREA_SIZE;
unsigned int to_sur_free_;

std::set<NyuObject*> rs_;

std::list<NyuContext*> contexts_;

}

void* NyuGC::allocate(size_t size) {
    if (new_free_ + size >= NEW_AREA_SIZE - sizeof(AllocInfo)) {
        minorGC();
    }

    AllocInfo* mem = reinterpret_cast<AllocInfo*> (&young_heap_[new_free_]);

    mem->remembered = 0;
    mem->forwarded = 0;
    mem->age = 0;
    mem->size = size;

    new_free_ += size + sizeof(AllocInfo);

    return static_cast<void*> (reinterpret_cast<char*> (mem)
            + sizeof(AllocInfo));
}

void NyuGC::minorGC() {
    to_sur_free_ = to_sur_start_;

    for (std::list<NyuContext*>::iterator it = contexts_.begin(); it
            != contexts_.end(); ++it) {
        NyuContext* c = (*it);
        std::vector<NyuSymref*>& symbols = c->symbols_;
        NyuScope* scope = c->current_scope_;
        while (scope) {
            for (int i = 0, j = scope->argc_; i < j; i++) {
                if (!NYU_VALUE_IS_OBJECT(scope->argv_[i]))
                    continue;

                NyuObject* obj = NYU_VALUE_UNTAG_OBJECT(scope->argv_[i]);
                scope->argv_[i] = NYU_OBJECT_TO_VALUE(copy(obj));
            }

            for (std::vector<unsigned int>::iterator it2 =
                    scope->scope_symbols_.begin(); it2
                    != scope->scope_symbols_.end(); ++it2) {
                NyuSymref* ref = symbols[*it2];

                if (!NYU_VALUE_IS_OBJECT(ref->value))
                    continue;

                NyuObject* obj = NYU_VALUE_UNTAG_OBJECT(ref->value);
                ref->value = NYU_OBJECT_TO_VALUE(copy(obj));
            }

            scope = scope->parent_;
        }
    }

    // 記憶集合
    for (std::set<NyuObject*>::iterator it = rs_.begin(), end = rs_.end();
            it != end; ++it) {
        bool has_new_obj = false;
        (*it)->copyChildren(&has_new_obj);
        if (!has_new_obj) {
            rs_.erase(it);
        }
    }

    // デストラクタを呼び出す
    unsigned int pos = from_sur_start_;
    AllocInfo* mem = reinterpret_cast<AllocInfo*>(&young_heap_[pos]);
    while (mem->size) {
        if (!mem->forwarded) {
            NyuObject* obj = reinterpret_cast<NyuObject*>(
                    &young_heap_[pos + sizeof(AllocInfo)]);
            obj->~NyuObject();
        }

        pos += mem->size + sizeof(AllocInfo);
        mem = reinterpret_cast<AllocInfo*>(&young_heap_[pos]);
    }

    pos = 0;
    mem = reinterpret_cast<AllocInfo*>(young_heap_);
    while (mem->size && pos + mem->size < NEW_AREA_SIZE - sizeof(AllocInfo)) {
        if (!mem->forwarded) {
            NyuObject* obj = reinterpret_cast<NyuObject*>(
                    &young_heap_[pos + sizeof(AllocInfo)]);
            obj->~NyuObject();
        }

        pos += mem->size + sizeof(AllocInfo);
        mem = reinterpret_cast<AllocInfo*>(&young_heap_[pos]);
    }

    std::swap(from_sur_start_, to_sur_start_);

    new_free_ = 0;
}

void NyuGC::majorGC() {
    // TODO
}

NyuObject* NyuGC::copy(NyuObject* obj, bool* is_new_area) {
    NyuObject* p1 = reinterpret_cast<NyuObject*> (young_heap_);
    NyuObject* p2 = reinterpret_cast<NyuObject*> (young_heap_ + YOUNG_HEAP_SIZE);
    if (obj < p1 || p2 <= obj)
        return obj;

    AllocInfo* mem = reinterpret_cast<AllocInfo*>(
            reinterpret_cast<char*>(obj) - sizeof(AllocInfo));

    if (!mem->forwarded) {
        if (mem->age < AGE_MAX && to_sur_free_ - to_sur_start_ < SUR_AREA_SIZE) {
            AllocInfo* mem2 = reinterpret_cast<AllocInfo*>(
                    &young_heap_[to_sur_free_]);

            ::memmove(&young_heap_[to_sur_free_ + sizeof(AllocInfo)], obj, mem->size);

            mem->forwarded = 1;
            *(reinterpret_cast<NyuObject**>(obj))
                    = reinterpret_cast<NyuObject*>(
                            &young_heap_[to_sur_free_ + sizeof(AllocInfo)]);

            mem2->remembered = mem->remembered;
            mem2->forwarded = 0;
            mem2->age = mem->age + 1;
            mem2->size = mem->size;

            to_sur_free_ += mem2->size + sizeof(AllocInfo);

            // 次の領域を初期化しておく
            mem2 = reinterpret_cast<AllocInfo*>(&young_heap_[to_sur_free_]);
            mem2->size = 0;

            if (is_new_area)
                *is_new_area = true;

            // objが参照しているオブジェクトをコピーする
            (*(reinterpret_cast<NyuObject**>(obj)))->copyChildren();
        } else {
            // 一旦旧世代領域は単に実ヒープ領域ってことで。
            mem->forwarded = 1;

            NyuObject* p = reinterpret_cast<NyuObject*>(::operator new(mem->size));

            ::memcpy(p, obj, mem->size);

            *(reinterpret_cast<NyuObject**>(obj)) = p;

            // objが参照しているオブジェクトをコピーする
            bool has_new_obj = false;
            p->copyChildren(&has_new_obj);
            if (has_new_obj)
                rs_.insert(p);
        }
    }

    // 最終的にobjの位置に移動先ポインタを書く
    return *(reinterpret_cast<NyuObject**>(obj));
}

void NyuGC::addContext(NyuContext* c) {
    contexts_.push_back(c);
}

void NyuGC::removeContext(NyuContext* c) {
    contexts_.remove(c);
}
